<?php
/**/
namespace LIYunde\Cloud\Api;

use LIYunde\Cloud\Api\Common\DataNameBuilder;
use LIYunde\Cloud\Api\Common\OpenApiConfig;
use LIYunde\Cloud\Api\Common\SdkErrors;
use LIYunde\Cloud\Api\Error\OpenSdkException;
use LIYunde\Cloud\Api\Request\BaseRequest;
use LIYunde\Cloud\Api\Request\OpenRequest;
use LIYunde\Cloud\Api\Response\BaseResponse;
use LIYunde\Cloud\Api\Util\OpenApiSignature;
use Monolog\Logger;

/**
 * Class OpenApiClient
 * @author liyunde
 * @since 2020/8/23
 *
 * @package LIYunde\Cloud\Api
 */
class OpenApiClient implements \JsonSerializable {

    const ERROR_RESPONSE_KEY = 'error_response';

    const REQUEST_ID = 'request_id';

    const SDK_VERSION = '2020.8.23';

    const SUCCESS = "10000";

    public static function version() {
        return self::SDK_VERSION;
    }

    /**
     * @var \Monolog\Logger $logger
     */
    private $logger;

    /**
     * 接口请求url
     */
    private $gateway;

    /**
     * 平台提供的appId
     */
    private $appId;

    /**
     * 开放平台提供的私钥
     */
    private $privateKey;

    /**
     * 开放平台提供的公钥
     */
    private $publicKeyPlatform;

    /**
     * 配置项
     * @var OpenApiConfig
     */
    private $openConfig;

    /**
     * 请求对象
     * @var OpenRequest
     */
    private $openRequest;

    /**
     * 节点处理
     * @var DataNameBuilder
     */
    private $dataNameBuilder;

    private $validResponseSign;

    /**
     * @param bool $validResponseSign
     */
    public function setValidResponseSign($validResponseSign) {
        $this->validResponseSign = $validResponseSign;
    }

    /**
     * 构建请求客户端
     *
     * @param $gateway string 接口url
     * @param $appId string 接口url
     * @param $privateKeyIsv string 平台分配的私钥
     * @param $publicKeyPlatform string 平台分配的公钥
     *
     * @param OpenApiConfig|null $openConfig 配置项
     */
    public function __construct($gateway, $appId, $privateKeyIsv, $publicKeyPlatform = null, OpenApiConfig $openConfig = null) {
        $this->gateway = $gateway;
        $this->appId = $appId;

        //如果我们拿到的公钥私钥是一行的,需要做下面处理

        $privateKey = chunk_split($privateKeyIsv, 64, "\n");

        $this->privateKey = "-----BEGIN RSA PRIVATE KEY-----\n{$privateKey}-----END RSA PRIVATE KEY-----\n";

        if ($publicKeyPlatform) {
            $publicKey = chunk_split($publicKeyPlatform, 64, "\n");
            $this->publicKeyPlatform = "-----BEGIN PUBLIC KEY-----\n{$publicKey}-----END PUBLIC KEY-----\n";
        }

        $this->validResponseSign = !!$publicKeyPlatform;

        if ($openConfig == null) {
            $openConfig = new OpenApiConfig();
            $openConfig->setSdkVersion(self::SDK_VERSION);
            $openConfig->buildUserAgent();
        }

        $this->openConfig = $openConfig;

        $this->openRequest = new OpenRequest($openConfig);

        $this->dataNameBuilder = $this->openConfig->getDataNameBuilder();
    }

    public function __destruct() {
    }

    /**
     * @param Logger $logger
     */
    public function setLogger($logger) {
        $this->logger = $logger;
        $this->openRequest->setLogger($logger);
        $this->openConfig->setLogger($logger);
    }

    /**
     * 请求接口,默认调用方法,返回对应的Response
     *
     * @param $request BaseRequest 请求对象
     * @param $accessToken string token
     *
     * @return BaseResponse 返回对象
     */
    public function execute(BaseRequest $request, $accessToken = null) {
        $requestForm = $request->createRequestForm($this->openConfig);
        // 表单数据
        $form = $requestForm->getForm();

        if ($accessToken != null) {
            $form[$this->openConfig->getAccessTokenName()] = $accessToken;
        }

        $form[$this->openConfig->getAppKeyName()] = $this->appId;

        $content = OpenApiSignature::getSignContent($form);

        try {
            $sign = OpenApiSignature::rsaSign($content, $this->privateKey, $this->openConfig->getCharset(), $this->openConfig->getSignType());
        } catch (Error\OpenSignException $e) {
            throw new OpenSdkException("构建签名错误", $e->getCode(), $e);
        }

        $form[$this->openConfig->getSignName()] = $sign;

        $requestForm->setForm($form);

        $resp = $this->doExecute($this->gateway, $requestForm, []);

        if ($this->logger && $this->logger->isHandling(Logger::DEBUG)) {

            $args = json_encode($form, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

            $form_str = http_build_query($form);

            $msg = <<<EOT
----------- 请求信息 -----------
待签名内容：$content
请求参数： $args
请求Form参数： $content
表单参数: $form_str
签名(sign)： $sign
----------- 返回结果 -----------
$resp
EOT;
            $this->logger->debug($msg);
        }

        return $this->parseResponse($resp, $request);
    }

    private function doExecute($url, $requestForm, $header) {
        return $this->openRequest->request($url, $requestForm, $header);
    }

    /**
     * 解析返回结果
     *
     * @param string $resp 返回结果
     * @param BaseRequest $request 请求对象
     *
     * @return BaseResponse 返回对于的Response对象
     */
    protected function parseResponse($resp, BaseRequest $request) {
        $method = $request->getApiMethodName();

        $rootNodeName = $this->dataNameBuilder->build($method);

        /**
         * @var \StdClass $jsonObject
         */
        $jsonObject = json_decode($resp);

        if ($this->logger && $this->logger->isHandling(Logger::DEBUG)) {
            $this->logger->debug("返回信息:$resp");
        }

        $errorResponseName = $this->openConfig->getErrorResponseName();

        if (property_exists($jsonObject, $errorResponseName)) {
            //有错误
            $rootNodeName = $errorResponseName;

            if (property_exists($jsonObject, $rootNodeName)) {
                return $jsonObject->{$rootNodeName};
            }
        }

        if (property_exists($jsonObject, $rootNodeName)) {
            $data = $jsonObject->{$rootNodeName};
        }

        // 是否要验证返回的sign
        if ($this->validResponseSign
            && property_exists($jsonObject, $this->openConfig->getSignName())
            && ($sign = $jsonObject->{$this->openConfig->getSignName()})
            && !empty($sign) && !empty($this->publicKeyPlatform)) {
            $signContent = $this->buildBizJson($rootNodeName, $resp);

            if (!$signContent || !$this->checkResponseSign($signContent, $sign)) {
                return SdkErrors::ResponseSignatureError();
            }
        }

        if (property_exists($jsonObject, self::REQUEST_ID)) {
            $data->{self::REQUEST_ID} = $jsonObject->{self::REQUEST_ID};
        }

        $data->success = self::SUCCESS == $data->code && (!property_exists($jsonObject, 'sub_code') || empty($jsonObject->sub_code));

        return $data;
    }

    /**
     * 构建业务json内容。
     * 假设返回的结果是：<br>
     * {"gateway_echo_demo_response":{"msg":"Success","code":"10000","name":"海底小纵队","id":1},"sign":"xxx"}
     * 将解析得到：<br>
     * {"msg":"Success","code":"10000","name":"海底小纵队","id":1}
     *
     * @param $rootNodeName string 根节点名称
     * @param $body string 返回内容
     *
     * @return string 返回业务json
     */
    protected function buildBizJson($rootNodeName, $body) {
        $indexOfRootNode = strpos($body, $rootNodeName);
        if ($indexOfRootNode < 0) {
            $rootNodeName = self::ERROR_RESPONSE_KEY;
            $indexOfRootNode = strpos($body, $rootNodeName);
        }

        $result = null;
        if ($indexOfRootNode > 0) {
            $result = $this->buildJsonNodeData($body, $rootNodeName, $indexOfRootNode + strlen($rootNodeName) + 2);
        }
        return $result;
    }

    /**
     * @param string $body 返回内容
     * @param string rootNodeName 根节点名称
     * @param string indexOfRootNode 根节点名称位置
     *
     * @return string 返回业务json内容
     */
    private function buildJsonNodeData($body, $rootNodeName, $indexOfRootNode) {
        //这里计算出"sign"字符串所在位置
        $indexOfSign = strpos($body, ",\"{$this->openConfig->getSignName()}\":");
        if ($indexOfSign < 0) {
            return false;
        }
        $length = $indexOfSign - $indexOfRootNode;
        // 根据起始位置和长度，截取出json：{"msg":"Success","code":"10000","name":"海底小纵队","id":1}
        return substr($body, $indexOfRootNode, $length);
    }

    /**
     * 校验返回结果中的sign
     *
     * @param string signContent 校验内容
     * @param string sign sign
     *
     * @return  boolean true：正确
     */
    protected function checkResponseSign($signContent, $sign) {
        if (empty($this->publicKeyPlatform)) {
            return false;
        }

        try {
            $charset = $this->openConfig->getCharset();
            $signType = $this->openConfig->getSignType();
            return OpenApiSignature::verifyContentRsaSign($signContent, $sign, $this->publicKeyPlatform, $charset, $signType);
        } catch (Error\OpenSignException $e) {
            if ($this->logger) {
                $this->logger->error("验证服务端sign出错,signContent：${$signContent}", $e->getTrace());
            }
            return false;
        }
    }

    public function __toString() {
        return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
    }

    public function jsonSerialize() {
        return get_object_vars($this);
    }
}
